package server;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.DocumentBuilder;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.Element;

import java.io.File;

/**
 * The ConfigurationManager class maintains the firewall configuration in a
 * persistent manner.
 * It allows getting/setting various configuration options, and stores them
 * in an XML file.
 */
public class ConfigurationManager {

    /**
     * Initializes the ConfigurationManager to use the given file.
     * Reads the data from the given configuration file, and creates a new one
     * if the file is not present.
     *
     * @param   configurationFileName   Filename to use
     *
     * @throws  CorruptConfigurationFileException - if config file is corrupt
     * @throws  FirwallException                  - on any other error
     */
    public ConfigurationManager(String configurationFileName) 
        throws CorruptConfigurationFileException, FirewallException {

        this.m_configrationFileName = configurationFileName;
        refreshFromFile();
    }

    /// @return Active ruleset
    public String getActiveRuleset() {
        return readNode(activeRulesetFieldName);
    }

    /**
     * Sets the active ruleset, in the ConfigurationManager and on the disk.
     *
     * @param   name    New value for active ruleset
     *
     * @throws  FirewallException - on any error
     */
    public void setActiveRuleset(String name) throws FirewallException {
        setNode(activeRulesetFieldName, name);
    }

    /// @return 'true' if log active, 'false' if not.
    public boolean getIsLogActive() {
        return
            (readNode(isLogActiveFieldName).equals("true")) ? true : false;
    }

    /**
     * Sets the log state, in the ConfigurationManager and on the disk.
     *
     * @param   logActive    New value for log state
     *
     * @throws  FirewallException - on any error
     */
    public void setIsLogActive(boolean logActive) throws FirewallException {
        setNode(isLogActiveFieldName, logActive ? "true" : "false");
    }

    /// @return DB IP
    public String getDBIP() {
        return readNode(dbIPFieldName);
    }

    /**
     * Sets the DB IP, in the ConfigurationManager and on the disk.
     *
     * @param   address    New value for DB IP
     *
     * @throws  FirewallException - on any error
     */
    public void setDBIP(String address) throws FirewallException {
        setNode(dbIPFieldName, address);
    }

    /// @return DB name
    public String getDBName() {
        return readNode(dbNameFieldName);
    }

    /**
     * Sets the DB name, in the ConfigurationManager and on the disk.
     *
     * @param   name    New value for DB name
     *
     * @throws  FirewallException - on any error
     */
    public void setDBName(String name) throws FirewallException {
        setNode(dbNameFieldName, name);
    }


    /// @return DB user
    public String getDBUser() {
        return readNode(dbUserFieldName);
    }

    /**
     * Sets the DB user, in the ConfigurationManager and on the disk.
     *
     * @param   user    New value for DB user
     *
     * @throws  FirewallException - on any error
     */
    public void setDBUser(String user) throws FirewallException {
        setNode(dbUserFieldName, user);
    }

    /// @return DB password
    public String getDBPassword() {
        return readNode(dbPasswordFieldName);
    }

    /**
     * Sets the DB password, in the ConfigurationManager and on the disk.
     *
     * @param   password    New value for DB password
     *
     * @throws  FirewallException - on any error
     */
    public void setDBPassword(String password) throws FirewallException {
        setNode(dbPasswordFieldName, password);
    }

    /**
     * Reads the current configuration from the configuration file.
     * If the file does not exist it is created with the default values.
     *
     * @throw CorruptConfigurationFileException - if config file is corrupt
     * @throw FirewallException                 - on any other exception
     */
    private void refreshFromFile() 
        throws CorruptConfigurationFileException, FirewallException {

        File file = new File(m_configrationFileName);
        if (!file.exists()) {
            createNewFile();
            return;
        }

        // File is present - read it
        DocumentBuilder builder = null;
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        try {
            builder = factory.newDocumentBuilder();
            // Parse and load into memory the Document
            m_document = builder.parse( new File(m_configrationFileName));
        } catch (Exception sxe) {
            throw new CorruptConfigurationFileException();
        }
    }

    /**
     * Reads the given node from the document.
     *
     * @param   name    Name of node to read
     *
     * @return  The node's data, or null if not found.
     */
    private String readNode(String name) {
        Node item = 
            m_document.getElementsByTagName(name).item(0);

        // If there's nothing there, return an empty string
        if (item.getChildNodes().item(0) == null) {
            return "";
        }

        return item.getChildNodes().item(0).getNodeValue();
    }

    /**
     * Updates a node's value in the configuration.
     *
     * @param   name    - Name of node to change
     * @param   value   - New value for node
     *
     * @throw   FirewallException - On any error
     */
    private void setNode(String name, String value) throws FirewallException {

        // Change a backup document, so that if we fail, we'l be consistent
        Document tempDocument = m_document;

        Node item = 
            m_document.getElementsByTagName(name).item(0);
        item.getChildNodes().item(0).setNodeValue(value);

        try {
            DOMUtil.writeXmlToFile(m_configrationFileName, m_document);
        } catch (Exception e) {
            // Error. Rollback what we did.
            m_document = tempDocument;
            throw new FirewallException("Failed updating configuration");
        }
    }

    /**
     * Creates a new file and initializes it with the default configuration
     * values.
     *
     * @throw   FirewallException - on any error
     */
    private void createNewFile() throws FirewallException {

        // Create a new Document object
        DocumentBuilder builder = null;
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        try{
            builder = factory.newDocumentBuilder();
            m_document = builder.newDocument();
        } catch (ParserConfigurationException e) {
            throw new FirewallException("Failed parsing config file");
        }

        // Create root Configuration element
        Element root = (Element)m_document.createElement("Configuration");
        m_document.appendChild(root);

        // Create all values within the Configuration element
        Node item;
        Node data;

        item = m_document.createElement(activeRulesetFieldName);
        data = m_document.createTextNode(activeRulesetDefault);
        item.appendChild(data);
        root.appendChild(item);

        item = m_document.createElement(isLogActiveFieldName);
        data = m_document.createTextNode(isLogActiveDefault ? "true" : "false");
        item.appendChild(data);
        root.appendChild(item);

        item = m_document.createElement(dbIPFieldName);
        data = m_document.createTextNode(dbIPDefault);
        item.appendChild(data);
        root.appendChild(item);

        item = m_document.createElement(dbNameFieldName);
        data = m_document.createTextNode(dbNameDefault);
        item.appendChild(data);
        root.appendChild(item);
        
        item = m_document.createElement(dbUserFieldName);
        data = m_document.createTextNode(dbUserDefault);
        item.appendChild(data);
        root.appendChild(item);

        item = m_document.createElement(dbPasswordFieldName);
        data = m_document.createTextNode(dbPasswordDefault);
        item.appendChild(data);
        root.appendChild(item);

        // Write the configuration to file
        try {
            DOMUtil.writeXmlToFile(m_configrationFileName, m_document);
        } catch (Exception e) {
            throw new FirewallException("Failed writing config to file");
        }
    }

    /// Field name for active ruleset
    private final String activeRulesetFieldName = "activeRuleset";

    /// Default active ruleset name, used when initializating config file
    private final String activeRulesetDefault = "default_rs";

    /// Field name for is log active
    private static final String isLogActiveFieldName = "logActive";

    /// Default for isLogActive, used when initializating config file
    private static final boolean isLogActiveDefault = false;

    /// Field name for DB IP address
    private static final String dbIPFieldName = "database_IP";

    /// Default for DB IP address, used when initializating config file
    private static final String dbIPDefault = "192.168.0.31";

    /// Field name for DB name
    private static final String dbNameFieldName = "database_name";

    /// Default for DB name address, used when initializating config file
    private static final String dbNameDefault = "FW";

    /// Field name for DB user
    private static final String dbUserFieldName = "database_user";

    /// Default for DB user, used when initializating config file
    private static final String dbUserDefault = "root";

    /// Field name for DB password
    private static final String dbPasswordFieldName = "database_password";

    /// Default for DB password, used when initializating config file
    private static final String dbPasswordDefault = "";

    /// Configuration filename
    String m_configrationFileName;

    /// DOM document for the configuration
    Document m_document;
};
